/**
 * 判断当前环境是否为 iOS 设备。
 *
 * @returns {boolean} 如果是iOS设备，返回true；否则返回false。
 */
function isIos() {
    const userAgent = navigator.userAgent.toLowerCase()

    return /iphone|ipad|ipod/.test(userAgent)
}

/**
 * 判断当前环境是否为 Android 设备。
 *
 * @returns {boolean} 如果是Android设备，返回true；否则返回false。
 */
function isAndroid() {
    const userAgent = navigator.userAgent.toLowerCase()

    return /android/.test(userAgent)
}

const AudioPermissionErrorType = {
    NO_USER_INTERACTION_ERROR: 'NoUserInteractionError',
    AUTOPLAY_BLOCKED: 'AutoplayBlocked',
    AUTOPLAY_UNKNOWN_ERROR: 'AutoplayUnknownError',

    SECURE_CONTEXT_ERROR: 'SecureContextError',
    PERMISSION_DENIED_PERMANENTLY: 'PermissionDeniedPermanently',
    NOT_ALLOWED_ERROR: 'NotAllowedError',
    MEDIA_DEVICES_API_UNAVAILABLE: 'MediaDevicesApiUnavailable',
    NOT_FOUND_ERROR: 'NotFoundError',
    NOT_READABLE_ERROR: 'NotReadableError',
    OVERCONSTRAINED_ERROR: 'OverconstrainedError',
    SECURITY_ERROR: 'SecurityError',
    MICROPHONE_UNKNOWN_ERROR: 'MicrophoneUnknownError',
    MICROPHONE_TIMEOUT_ERROR: 'MicrophoneTimeoutError',
}

class AudioPermissionError extends Error {
    constructor(name, message) {
        super(message)
        this.name = name
    }

    /**
     * 创建音频权限错误实例
     * @param {string} name - 错误类型
     * @param {string} [message] - 自定义错误消息
     * @returns {AudioPermissionError} 音频权限错误实例
     */
    static create(name, message) {
        const codeMessageMap = {
            [AudioPermissionErrorType.NO_USER_INTERACTION_ERROR]: '需要用户交互才能播放音频',
            [AudioPermissionErrorType.AUTOPLAY_BLOCKED]: '自动播放被浏览器阻止',
            [AudioPermissionErrorType.AUTOPLAY_UNKNOWN_ERROR]: '未知自动播放错误',

            [AudioPermissionErrorType.SECURE_CONTEXT_ERROR]: '麦克风权限需要在安全上下文（HTTPS）中使用',
            [AudioPermissionErrorType.PERMISSION_DENIED_PERMANENTLY]: '音频权限已被永久拒绝，需在浏览器设置中开启麦克风权限',
            [AudioPermissionErrorType.MEDIA_DEVICES_API_UNAVAILABLE]: 'MediaDevices API不可用',
            [AudioPermissionErrorType.NOT_ALLOWED_ERROR]: '麦克风权限被拒绝',
            [AudioPermissionErrorType.NOT_READABLE_ERROR]: '音频设备被其他应用程序占用',
            [AudioPermissionErrorType.NOT_FOUND_ERROR]: '未找到音频输入设备',
            [AudioPermissionErrorType.SECURITY_ERROR]: '安全策略阻止访问麦克风',
            [AudioPermissionErrorType.OVERCONSTRAINED_ERROR]: '音频设备不满足指定的约束条件',
            [AudioPermissionErrorType.MICROPHONE_UNKNOWN_ERROR]: '未知麦克风权限错误',
            [AudioPermissionErrorType.MICROPHONE_TIMEOUT_ERROR]: '获取麦克风权限超时，请检查设备权限设置或重试',
        }

        return new AudioPermissionError(name, message || codeMessageMap[name])
    }
}

/**
 * 音频播放权限管理类
 *
 * 根据MDN文档，音频播放权限需要满足以下条件之一：
 * 1. 音频被静音或音量为0
 * 2. 用户已与网站产生交互（点击、触摸、按键等）
 * 3. 网站已被加入自动播放白名单
 * 4. 通过Permissions Policy授权
 *
 * 跨平台实现策略：
 * - PC端：预检测AudioContext状态，如果suspended则监听用户交互
 * - iOS Safari：必须在用户交互事件的调用栈中调用resume()，需要50ms延迟确保状态变更
 * - Android：直接在用户交互后检测AudioContext状态
 *
 *
 *
 * 实现原理：
 *    PC:
 *      1.在PC端首先预检测AudioContext状态：
 *        如果状态是 "running" 状态则可以播放音频，
 *        如果状态不是 "running" 状态则需要监听用户交互事件，
 *      2.用户交互事件触发后，再检测AudioContext状态：
 *        如果状态是 "running" 状态则可以播放音频，
 *        如果状态不是 "running" 状态则抛出异常。
 *
 *    IOS:
 *      1.用户交互事件触发后，再检测AudioContext状态：
 *        如果状态是 "suspended" 则需要调用 resume 方法，并且需要延迟 50ms，
 *        如果状态不是 "suspended" 状态进行下一步判断，
 *      2.获取AudioContext状态：
 *        如果状态是 "running" 状态则可以播放音频，
 *        如果状态不是 "running" 状态则抛出异常。
 *
 *    Android:
 *      1.用户交互事件触发后，再检测AudioContext状态：
 *        如果状态是 "running" 状态则可以播放音频，
 *        如果状态不是 "running" 状态则抛出异常。
 */
class AudioPlaybackPermission {
    constructor() {
        this.userInteracted = false
        this.executeContext = null
        this.initPlaybackPermission()
    }

    /**
     * 初始化播放权限检测
     */
    initPlaybackPermission() {
        if (!isIos() && !isAndroid()) {
            this.checkInitialAudioContextState().catch(() => {
                this.setupUserInteractionListeners()
            })
        } else {
            this.setupUserInteractionListeners()
        }
    }

    /**
     * 检测初始AudioContext状态（仅PC平台）
     * @returns {Promise<void>}
     */
    async checkInitialAudioContextState() {
        const testContext = new (window.AudioContext || window.webkitAudioContext)()

        if (testContext.state === 'running') {
            this.userInteracted = true
        }

        await testContext.close()

        if (this.userInteracted) {
            return Promise.resolve()
        } else {
            return Promise.reject()
        }
    }

    /**
     * 设置用户交互监听器
     */
    setupUserInteractionListeners() {
        const interactionEvents = [
            'click',
            'touchstart',
            'touchend',
            'keydown',
            'mousedown',
            'pointerdown',
        ]

        const handleInteraction = () => {
            this.userInteracted = true

            interactionEvents.forEach((eventType) => {
                document.removeEventListener(eventType, handleInteraction)
            })
        }

        interactionEvents.forEach((eventType) => {
            document.addEventListener(eventType, handleInteraction, { passive: true, once: true })
        })
    }

    /**
     * 请求播放权限
     * 根据Web Audio API最佳实践实现
     * @returns {Promise<void>}
     */
    async requestPlaybackPermission() {
        let audioContext = null
        try {
            if (!this.userInteracted) {
                throw AudioPermissionError.create(AudioPermissionErrorType.NO_USER_INTERACTION_ERROR)
            }

            if (isIos() && this.executeContext) {
                await this.executeContext.requestMicrophonePermission()
            }

            audioContext = new (window.AudioContext || window.webkitAudioContext)()

            if (isIos() && audioContext.state === 'suspended') {
                await Promise.race([
                    audioContext.resume(),
                    new Promise((_, reject) =>
                        setTimeout(() => reject(AudioPermissionError.create(AudioPermissionErrorType.AUTOPLAY_BLOCKED)), 3000)
                    ),
                ])

                await new Promise(resolve => setTimeout(resolve, 50))
            }

            if (audioContext.state !== 'running') {
                throw AudioPermissionError.create(AudioPermissionErrorType.AUTOPLAY_BLOCKED)
            }

            return Promise.resolve()
        } catch (error) {
            if (error instanceof AudioPermissionError) {
                return Promise.reject(error)
            }

            const unknownError = AudioPermissionError.create(
                AudioPermissionErrorType.AUTOPLAY_UNKNOWN_ERROR,
                error instanceof Error ? error.message : undefined
            )
            return Promise.reject(unknownError)
        } finally {
            if (audioContext && audioContext.state !== 'closed') {
                try {
                    await audioContext.close()
                } catch { }
            }
        }
    }

    /**
     * 执行上下文设置
     * @param {AudioPermission} context 执行上下文
     */
    setExecuteContext(context) {
        this.executeContext = context
    }
}

/**
 * 麦克风权限管理类
 *
 * 根据MDN文档，getUserMedia需要满足：
 * 1. 安全上下文（HTTPS）
 * 2. 用户明确授权
 * 3. 顶级文档上下文或通过Permissions Policy授权的iframe
 *
 *
 * 权限状态说明：
 * - granted: 用户已授权，可直接访问麦克风
 * - denied: 用户已拒绝，需要用户手动在浏览器设置中重新开启
 * - prompt: 首次访问，会弹出授权对话框
 *
 *
 * 平台差异：
 * - 桌面端：通过浏览器原生授权对话框处理
 * - iOS Safari：权限被拒绝后，需要用户在系统设置中重新开启
 * - Android Chrome：权限处理与桌面端类似，但某些版本可能有缓存问题
 *
 *
 * 注意事项：
 * 1. 浏览器会永久缓存用户的权限决定
 * 2. 在非HTTPS环境下（除localhost外）会直接失败
 * 3. 某些浏览器在隐私模式下可能有不同行为
 * 4. iframe中使用需要正确配置Permissions Policy
 *
 *
 * 实现原理：
 * - PC平台：
 *     1. 通过授权弹框申请权限：
 *          如果用户拒绝了权限，会抛出异常
 *          如果用户点击了允许，会返回成功
 *     2.第二次申请权限，会根据用户第一次申请权限的结果：
 *          如果用户第一次申请权限失败，会抛出异常
 *          如果用户第一次申请权限成功，会返回成功
 * - 移动端：
 *     1. 通过授权弹框申请权限：
 *          如果用户拒绝了权限，会抛出异常
 *          如果用户点击了允许，会返回成功
 *      2.第二次申请权限，会根据用户第一次申请权限的结果：
 *          如果用户第一次申请权限失败，会抛出异常
 *          如果用户第一次申请权限成功，会返回成功
 *
 *  iOS平台：
 *      1. 通过授权弹框申请权限：
 *          如果用户拒绝了权限，会抛出异常
 *          如果用户点击了允许，会返回成功
 *      2.第二次申请权限，会根据用户第一次申请权限的结果：
 *          如果用户第一次申请权限失败，会抛出异常
 *          如果用户第一次申请权限成功，会返回成功
 *
 *
 */
class MicrophonePermission {
    constructor() {
        this.microphonePermissionGranted = false
        this.permissionDeniedPermanently = false
    }

    /**
     * 检查是否在安全上下文中
     * @returns {boolean}
     */
    isSecureContext() {
        return window.isSecureContext || location.protocol === 'https:' || location.hostname === 'localhost'
    }

    /**
     * 带超时的getUserMedia调用
     * @param {MediaStreamConstraints} constraints - 媒体约束
     * @param {number} [timeoutMs=10000] - 超时时间（毫秒）
     * @returns {Promise<MediaStream>}
     */
    async getUserMediaWithTimeout(constraints, timeoutMs = 10000) {
        return new Promise((resolve, reject) => {
            const timeoutId = setTimeout(() => {
                reject(AudioPermissionError.create(
                    AudioPermissionErrorType.MICROPHONE_TIMEOUT_ERROR
                ))
            }, timeoutMs)

            navigator.mediaDevices.getUserMedia(constraints)
                .then((stream) => {
                    clearTimeout(timeoutId)
                    resolve(stream)
                })
                .catch((error) => {
                    clearTimeout(timeoutId)
                    reject(error)
                })
        })
    }

    /**
     * 请求麦克风权限
     * 根据MediaDevices.getUserMedia规范实现
     * @returns {Promise<void>}
     */
    async requestMicrophonePermission() {
        try {

            // 快速检查权限状态（不触发系统弹框）
            if (navigator.permissions) {
                try {
                    const result = await navigator.permissions.query({ name: 'microphone' })
                    if (result.state === 'granted') {
                        this.microphonePermissionGranted = true
                        return Promise.resolve()
                    }
                    else if (result.state === 'denied') {
                        this.permissionDeniedPermanently = true
                        throw AudioPermissionError.create(AudioPermissionErrorType.PERMISSION_DENIED_PERMANENTLY)
                    }
                }
                catch {

                }
            }

            if (!this.isSecureContext()) {
                throw AudioPermissionError.create(
                    AudioPermissionErrorType.SECURE_CONTEXT_ERROR
                )
            }

            if (this.permissionDeniedPermanently) {
                throw AudioPermissionError.create(AudioPermissionErrorType.PERMISSION_DENIED_PERMANENTLY)
            }

            if (this.microphonePermissionGranted) {
                return Promise.resolve()
            }

            if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                throw AudioPermissionError.create(
                    AudioPermissionErrorType.MEDIA_DEVICES_API_UNAVAILABLE
                )
            }

            const stream = await this.getUserMediaWithTimeout({
                audio: {
                    echoCancellation: true,
                    noiseSuppression: true,
                    autoGainControl: true,
                },
            })

            this.microphonePermissionGranted = true

            stream.getTracks().forEach((track) => {
                track.stop()
            })

            return Promise.resolve()
        } catch (error) {
            this.microphonePermissionGranted = false

            if (error instanceof AudioPermissionError) {
                return Promise.reject(error)
            }

            if (error instanceof DOMException) {
                let errorType

                switch (error.name) {
                    case 'NotFoundError':
                        errorType = AudioPermissionErrorType.NOT_FOUND_ERROR
                        break
                    case 'NotAllowedError':
                        this.permissionDeniedPermanently = true
                        errorType = AudioPermissionErrorType.NOT_ALLOWED_ERROR
                        break
                    case 'NotReadableError':
                        errorType = AudioPermissionErrorType.NOT_READABLE_ERROR
                        break
                    case 'OverconstrainedError':
                        errorType = AudioPermissionErrorType.OVERCONSTRAINED_ERROR
                        break
                    case 'SecurityError':
                        errorType = AudioPermissionErrorType.SECURITY_ERROR
                        break
                    default:
                        errorType = AudioPermissionErrorType.MICROPHONE_UNKNOWN_ERROR
                }

                const audioError = AudioPermissionError.create(errorType)
                return Promise.reject(audioError)
            }

            const unknownError = AudioPermissionError.create(
                AudioPermissionErrorType.MICROPHONE_UNKNOWN_ERROR,
                error instanceof Error ? error.message : undefined
            )
            return Promise.reject(unknownError)
        }
    }

}

/**
 * 统一的音频权限管理类
 */
class AudioPermission {
    constructor() {
        this.playbackPermission = new AudioPlaybackPermission()
        this.microphonePermission = new MicrophonePermission()

        this.playbackPermission.setExecuteContext(this)
    }

    /**
     * 请求音频播放权限
     * @returns {Promise<void>}
     */
    async requestPlaybackPermission() {
        return this.playbackPermission.requestPlaybackPermission()
    }

    /**
     * 请求麦克风权限
     * @returns {Promise<void>}
     */
    async requestMicrophonePermission() {
        return this.microphonePermission.requestMicrophonePermission()
    }
}

let audioPermissionInstance = null

/**
 * 创建或获取音频权限管理实例
 * @returns {AudioPermission}
 */
function createAudioPermission() {
    if (!audioPermissionInstance) {
        audioPermissionInstance = new AudioPermission()
    }
    return audioPermissionInstance
}

export default createAudioPermission
